package com.haiqiu.syslog.aspect;

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import com.haiqiu.common.exception.LimitAccessException;
import com.haiqiu.syslog.annotation.Limit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * @author HaiQiu
 * 基于Guava限流
 */
@Slf4j
@Aspect
@Component
@Order(1)
public class LimitAspect {

    // 存放RateLimiter,一个url对应一个令牌桶
    private Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();

    @Pointcut("@annotation(com.haiqiu.syslog.annotation.Limit)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object obj = null;
        // 获取注解
        Limit limit = ((MethodSignature) point.getSignature()).getMethod()
                .getAnnotation(Limit.class);
        // 获取request,然后获取请求的url，存入map中
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        String url = request.getRequestURI();
        // 若获取注解不为空
        if (limit != null) {
            // 获取注解的permitsPerSecond与timeout
            double permitsPerSecond = limit.perSecond();
            int timeout = limit.timeOut();
            TimeUnit timeUnit = limit.unit();
            RateLimiter rateLimiter = null;
            // 判断map集合中是否有创建有创建好的令牌桶
            // 若是第一次请求该url，则创建新的令牌桶
            if (!limitMap.containsKey(url)) {
                // 创建令牌桶
                rateLimiter = RateLimiter.create(permitsPerSecond);
                limitMap.put(url, rateLimiter);
                System.out.println("请求======>" + url + "创建令牌桶，容量为：" + permitsPerSecond);
            }
            // 否则从已经保存的map中取
            rateLimiter = limitMap.get(url);
            // 若得到令牌
            if (rateLimiter.tryAcquire(timeout, timeUnit)) {
                // 开始执行目标controller
                obj = point.proceed();
            } else {
                // 否则直接返回错误信息
                log.error(limit.name() + " 接口并发量过大执行限流");
                throw new LimitAccessException("网络异常，请稍后重试！");
            }
        }
        return obj;
    }

}